跳到主要内容

Python 基础速通(运维向)

这个系列面向有 shell 基础的运维工程师,目标很明确:用 Python 写运维脚本。第 1 篇快速过一遍基础和工具链,从第 2 篇开始全是运维场景。默认环境 macOS / Linux,Python 3.12+,所有示例都可以直接照抄运行。

为什么是 Python​

shell 脚本写着写着总会遇到几个坎,这时候就该换 Python 了:

  • 脚本超过 100 行:逻辑一复杂,shell 的调试和可读性就崩盘。
  • 要处理结构化数据:JSON、YAML、CSV,shell 靠 awk/sed/jq 硬啃,Python 原生支持。
  • 要调 API:云厂商、监控平台、内部系统全是 HTTP 接口,Python 发请求是三行的事。

运维场景里 Python 的位置:单机上的小工具、巡检脚本、日志分析、一次性数据处理。多台机器的批量编排交给 Ansible 这类工具(思路见 Ansible 核心概念与架构),Python 负责把单点的事情做漂亮。

备注

不是二选一。实际工作流常常是 Ansible 批量下发,机器上跑的那个脚本本体就是 Python。

安装与版本​

系统自带的 python3 别乱动​

macOS 装了 Xcode 命令行工具后自带 /usr/bin/python3,Linux 发行版也几乎都有。注意两点:

  • 系统 Python 是给系统工具用的,别拿 sudo pip install 往里面装包,更别升级或卸载它——Ubuntu 的 apt、一些系统组件都依赖自带 Python,动坏了系统功能会跟着挂。
  • 系统自带版本通常偏旧,写自己的脚本建议单独装一份新的。

用 pyenv 管理多版本​

多版本管理的思路和 Node 的 nvm 一模一样(不熟的话可以看 nvm 的安装与镜像配置):把多个 Python 版本装进用户目录,一条命令切换,不碰系统 Python。

# macOS
brew install pyenv

# Linux 用官方安装脚本
curl https://pyenv.run | bash

按安装完的提示把初始化语句写进 ~/.zshrc 或 ~/.bashrc,然后:

pyenv install 3.12      # 装一个 3.12 最新小版本
pyenv global 3.12 # 设为默认
python3 -V # 验证
Python 3.12.7
提示

不想装版本管理器也行:macOS 直接 brew install python@3.12,Ubuntu 用 sudo apt install python3,只要版本 ≥ 3.10 本系列的示例都能跑。

虚拟环境​

为什么必须隔离:不同项目依赖的包版本会打架。项目 A 要 requests 2.28,项目 B 要 2.31,装在一个环境里必有一个不开心。虚拟环境给每个项目一个独立的包目录,互不干扰。

标准库自带的 venv 就够用,不用装额外工具:

cd ~/ops-scripts
python3 -m venv .venv # 建虚拟环境,目录名约定叫 .venv
source .venv/bin/activate # 激活,提示符前会出现 (.venv)
pip install requests # 装包装进 .venv,不碰系统
deactivate # 退出虚拟环境

激活后 python、pip 都指向 .venv 里的那份,写脚本、跑脚本都在激活状态下进行。

.venv 不进版本库,.gitignore 里加上:

.venv/
__pycache__/

pip 与国内镜像​

默认源 PyPI 在国外,直连经常慢到怀疑人生。临时用清华镜像:

pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple

一劳永逸的做法是写进配置文件 ~/.pip/pip.conf(macOS / Linux 通用):

[global]
index-url = https://pypi.tuna.tsinghua.edu.cn/simple

日常高频命令:

pip install requests          # 装包
pip install -U requests # 升级到最新版
pip list # 列出已装的包
pip show requests # 看某个包的版本、依赖详情
pip freeze > requirements.txt # 导出精确版本清单
pip install -r requirements.txt # 在另一台机器上按清单复现

freeze 导出的清单会锁定每一个包的精确版本,交给同事或部署到服务器时用 -r 一键复现,环境一致性靠它保证。

语法速览​

只挑运维脚本里天天用的,够写 90% 的巡检和小工具。

变量与四种容器​

name = 'web01'                                # 字符串
port = 22 # 数字

servers = ['web01', 'web02', 'db01'] # list:有序、可改
server = {'name': 'web01', 'ip': '10.0.0.11'} # dict:键值对
zones = {'cn-north', 'cn-south'} # set:自动去重
addr = ('10.0.0.11', 22) # tuple:不可改

list 和 dict 用得最多;set 常用来去重;tuple 一般是函数返回多个值时顺手用。

切片​

servers = ['web01', 'web02', 'db01', 'db02', 'cache01']

servers[0] # 'web01',下标从 0 开始
servers[-1] # 'cache01',负数从尾部数
servers[1:3] # ['web02', 'db01'],含头不含尾
servers[:2] # ['web01', 'web02']

字典取值:get 与默认值​

直接 d['key'] 遇到键不存在会抛 KeyError,运维数据经常缺字段,用 get 更稳:

host = {'ip': '10.0.0.11'}

host.get('ip') # '10.0.0.11'
host.get('port') # None,不报错
host.get('port', 22) # 22,键不存在时给默认值

判断与循环​

usage = 87

if usage >= 90:
print('磁盘告急')
elif usage >= 80:
print('磁盘偏高')
else:
print('正常')

for server in ['web01', 'web02']:
print('巡检', server)

retry = 0
while retry < 3:
retry += 1
print(f'第 {retry} 次重试')

列表推导式​

一行完成「过滤 + 变形」,处理文件名、IP 列表时极顺手:

files = ['a.log', 'b.txt', 'c.log']
logs = [f for f in files if f.endswith('.log')]
print(logs) # ['a.log', 'c.log']

函数与默认参数​

def check_disk(path='/', threshold=80):
"""使用率超过 threshold 返回 True"""
usage = get_usage(path) # 假设有这个函数
return usage > threshold

默认参数让调用方少写字:check_disk() 等价于 check_disk('/', 80)。

try/except 抓异常​

运维脚本面对的环境不干净:文件可能不存在、权限可能不够。把可能出错的代码包进 try:

try:
with open('/etc/shadow', encoding='utf-8') as f:
f.read()
except PermissionError:
print('权限不够,需要 sudo')
except FileNotFoundError:
print('文件不存在')

抓具体异常类型,别用裸 except: 把所有错误(包括 Ctrl+C)都吞掉。

with open 读写文件​

# 读:with 块结束自动关文件,不怕忘
with open('/etc/hosts', encoding='utf-8') as f:
content = f.read()

# 写:'w' 覆盖写,'a' 追加写
with open('/tmp/result.txt', 'w', encoding='utf-8') as f:
f.write('巡检完成\n')

读大文件别 read() 一次性吞,逐行迭代:for line in f:。

f-string 格式化​

字符串前加 f,花括号里直接放变量和表达式,Python 3.6+ 的标配写法:

name = 'web01'
usage = 87.456
print(f'主机 {name} 磁盘使用率 {usage:.1f}%')
主机 web01 磁盘使用率 87.5%

:.1f 是格式说明,表示保留一位小数。

一个能跑的运维小脚本​

把上面的语法串起来:读 /etc/hosts,统计里面一共映射了多少个主机名(跳过空行和注释)。

存成 count_hosts.py:

#!/usr/bin/env python3
"""统计 /etc/hosts 中有效主机名条目的数量"""


def count_hosts(path='/etc/hosts'):
total = 0
with open(path, encoding='utf-8') as f:
for line in f:
line = line.split('#', 1)[0].strip() # 去掉行尾注释
if not line:
continue
total += len(line.split()) - 1 # 首列是 IP,其余都是主机名
return total


if __name__ == '__main__':
print(f'/etc/hosts 里共有 {count_hosts()} 个主机名条目')

运行:

python3 count_hosts.py
/etc/hosts 里共有 5 个主机名条目

两个约定解释一下:

  • 第一行 shebang(#!/usr/bin/env python3)让脚本可以 chmod +x 后直接 ./count_hosts.py 执行。
  • if __name__ == '__main__' 块里的代码只在直接运行脚本时执行,文件被别人 import 时不触发,写入口脚本都这么收尾。

常见坑​

  • python 与 python3 不是一个东西:很多系统上 python 不存在,或指向古董 Python 2。命令行里固定用 python3 / pip3;进了虚拟环境后 python 才安全地指向 3.x。
  • 全局 pip install 污染系统:新版 pip 会直接拒绝并报 externally-managed-environment(PEP 668),这是好事,逼你回虚拟环境。别加 --break-system-packages 硬来。
  • 缩进错误:Python 用缩进划分代码块,混用 Tab 和空格会报 TabError。编辑器设置成「Tab 展开为 4 个空格」,团队统一就没事了。

小结​

  • 脚本超 100 行、要处理 JSON/YAML、要调 API——这三种情况放下 shell,换 Python。
  • 系统 Python 别动,自己的版本用 pyenv 或 brew/apt 装,python3 -V 验证。
  • 每个项目一个 .venv 虚拟环境,pip 配清华镜像,freeze 锁版本保证可复现。
  • 语法记住四种容器、get 取字典、推导式、try/except、with open、f-string,就够开工了。

基础过关后,下一篇进入正题:不用装任何第三方库,标准库里的 os、subprocess、shutil、logging、argparse 等模块就能把日常运维脚本写出来。